package com.demo.agent.tool.jad;

import com.demo.agent.MyAgent;
import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.loader.LoaderException;
import org.jd.core.v1.api.printer.Printer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author: huangjiazhen
 * @create: 2023/11/23 10:48
 * @description:
 **/
public class Jad {

    private static Loader loader = new Loader() {
        @Override
        public boolean canLoad(String s) {
            return loadClass(s) != null;
        }

        @Override
        public byte[] load(String s) throws LoaderException {
            InputStream is = loadClass(s);
            if (is == null) {
                return null;
            } else {
                try (InputStream in = is; ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[1024];
                    int read = in.read(buffer);

                    while (read > 0) {
                        out.write(buffer, 0, read);
                        read = in.read(buffer);
                    }

                    return out.toByteArray();
                } catch (IOException e) {
                    throw new LoaderException(e);
                }
            }
        }

        private InputStream loadClass(String internalName) {
            InputStream is = this.getClass().getResourceAsStream("/" + internalName.replaceAll("\\.", "/") + ".class");
            if (is == null && MyAgent.instrumentation != null) {
                for (Class allLoadedClass : MyAgent.instrumentation.getAllLoadedClasses()) {
                    if (allLoadedClass.getName().equals(internalName)) {
                        is = allLoadedClass.getResourceAsStream("/" + internalName.replaceAll("\\.", "/") + ".class");
                        break;
                    }
                }
            }
            return is;
        }
    };

    private static Printer printer = new Printer() {
        protected static final String TAB = "  ";
        protected static final String NEWLINE = "\n";
        protected int indentationCount = 0;
        protected StringBuilder sb = new StringBuilder();

        @Override
        public String toString() {
            return sb.toString();
        }

        @Override
        public void start(int i, int i1, int i2) {

        }

        @Override
        public void end() {

        }

        @Override
        public void printText(String s) {
            sb.append(s);
        }

        @Override
        public void printNumericConstant(String s) {
            sb.append(s);
        }

        @Override
        public void printStringConstant(String s, String s1) {
            sb.append(s);
        }

        @Override
        public void printKeyword(String s) {
            sb.append(s);
        }

        @Override
        public void printDeclaration(int i, String s, String s1, String s2) {
            sb.append(s1);
        }

        @Override
        public void printReference(int i, String s, String s1, String s2, String s3) {
            sb.append(s1);
        }

        @Override
        public void indent() {
            this.indentationCount++;
        }

        @Override
        public void unindent() {
            this.indentationCount--;
        }

        @Override
        public void startLine(int lineNumber) {
            for (int i = 0; i < indentationCount; i++) sb.append(TAB);
        }

        @Override
        public void endLine() {
            sb.append(NEWLINE);
        }

        @Override
        public void extraLine(int i) {
            while (i-- > 0) sb.append(NEWLINE);
        }

        @Override
        public void startMarker(int i) {

        }

        @Override
        public void endMarker(int i) {

        }
    };

    public static String decompiler(String className) throws Exception {
        ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler();
        decompiler.decompile(loader, printer, className);
        return printer.toString();
    }
}
